package com.jiatuxueyuan.the4.remoteio;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 启动一个基于Netty的TCP服务端，可支持数十万客户端通过TCP方式接入
 */
public class TcpServer {

    /**
     * 保存所有当前连接在线的客户端通道，同时为了将来可以针对特定的某个客户端发送消息，在Channel中放了每个客户端的MAC地址
     */
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    static final boolean SSL = System.getProperty("ssl") != null;
    // Use the same default port with the telnet example so that we can use the telnet client example to access it.
    static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "4444" : "4444"));

    private String name;

    private ChannelFuture serverChannelFuture;

    private static Logger logger = LoggerFactory.getLogger(TcpServer.class);

    public TcpServer(String name) {
        this.name = name;
    }

    /**
     * 这个方法很重要，往指定的客户端发送字节流，异步发送，这个一般是用在服务端往指定的客户端推送的时候用到的，每个服务端进程往往会接收数十万，上百万客户端TCP连接，
     * 假设现在要往其中某个客户端推消息（比如A给B发微信，那必须得找出B当前连在哪个服务端进程上，然后从这个服务端进程中已经建立的上百万TCP连接中，准确的找出B的这条TCP连接，通过这个TCP连接发消息出去），
     * 我们就要遍历ChannelGroup，
     *
     * @param mac       客户端MAC地址，唯一标识
     * @param msg       要发出去的字节流
     */
    public void sendToClient(String mac, ByteBuf msg) {
        for(Channel channel : channelGroup) {
            Attribute<String> value = channel.attr(AttributeKey.valueOf("client_mac"));
            if(value != null && value.get() != null && value.get().equals(mac)) {
                ChannelFuture future =  channel.writeAndFlush(msg);
//                logger.info(String.format("发送消息给客户端：%s，消息内容：%s", mac, ByteBufUtil.hexDump(msg)));
                break;
            }
        }
    }

    public void start() throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() + 1);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1000)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            p.addLast(
//                                    new StringEncoder(CharsetUtil.UTF_8),
//                                    new LineBasedFrameDecoder(8192),
//                                    new StringDecoder(CharsetUtil.UTF_8),
//                                    new ChunkedWriteHandler(),
                                    new IdleStateHandler(60, 30, 0),//60秒收不到客户端消息就关闭连接，30秒没有发给客户端的消息就自动发一个消息出去
                                    new AutoCloseChannelHandler(),
                                    new Zlan6842InboundHandler(channelGroup),
                                    new H2Handler());
                        }
                    });
            // Start the server.
            serverChannelFuture = b.bind("192.168.2.66", PORT).sync();
            System.out.println("启动成功，在192.168.2.66:4444监听");
            // Wait until the server socket is closed.
            serverChannelFuture.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            System.out.println("收到关闭服务端进程的命令，已成功关闭...");
        }
    }

    public void shutdown() {
        serverChannelFuture.channel().close();
    }
}
